/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package gsim.editor;

import java.awt.Image;
import sim.component.ParametricPath;
import sim.component.Obstacle;
import sim.component.agent.AgentParameters;
import sim.component.agent.Agent;
import sim.component.RandomPath;
import sim.comms.Sensor;
import sim.component.team.LocationGenerator;
import sim.component.team.Team;
import java.awt.event.ActionEvent;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import sim.SimComponent;
import sim.tasks.Router;
import sim.tasks.TaskChooser;
import sim.tasks.Tasker;

/**
 * This represents the master table of actions supported within the simulation for setting and instantiating basic properties.
 *
 * @author ae3263
 */
public class SimActions {
    
    static ImageIcon loadIcon(String path) {
        URL url = SimActions.class.getResource("/gsim/icons/"+path+".png");
        if (url == null) {
            System.out.println("Unable to load icon " + path);
            return null;
        }
        return new ImageIcon(url);
    }

    public static final HashMap<Class, Action> CLASS_ACTIONS = new HashMap<Class, Action>() {
        {
            put(Team.class, 
                    new GenericSetAction("Team", loadIcon("team"),
                    "A collection of players in the simulation",
                    Team.class, "addComponent", SimComponent.class));
            put(Agent.class, 
                    new GenericSetAction("Player", loadIcon("agent"),
                    "A single player in the simulation",
                    Agent.class, "addComponent", SimComponent.class));
            put(ParametricPath.class, 
                    new GenericSetAction("Parametric Path", loadIcon("path-agent"),
                    "A path generated by a paremetric curve",
                    ParametricPath.class, "addComponent", SimComponent.class));
            put(RandomPath.class, 
                    new GenericSetAction("Random Path", loadIcon("random-agent"),
                    "A path generated by a random walk",
                    RandomPath.class, "addComponent", SimComponent.class));
            put(Obstacle.class, 
                    new GenericSetAction("Obstacle", loadIcon("obstacle"),
                    "An immobile obstacle",
                    Obstacle.class, "addComponent", SimComponent.class));

            put(LocationGenerator.DELEGATE_INSTANCE.getClass(), 
                    new GenericSetAction("Delegate Locations", loadIcon("loc-delegate"),
                    "Delegate locations to players",
                    LocationGenerator.DELEGATE_INSTANCE, "setLocationGenerator", LocationGenerator.class));
            put(LocationGenerator.ORIGIN_INSTANCE.getClass(), 
                    new GenericSetAction("Start at Origin", loadIcon("loc-origin"),
                    "Start all players at origin",
                    LocationGenerator.ORIGIN_INSTANCE, "setLocationGenerator", LocationGenerator.class));
            put(LocationGenerator.Segment.class, 
                    new GenericSetAction("Start on Line", loadIcon("loc-line"),
                    "Start players along a line",
                    LocationGenerator.Segment.class, "setLocationGenerator", LocationGenerator.class));
            put(LocationGenerator.Circle.class, 
                    new GenericSetAction("Start on Circle", loadIcon("loc-circle"),
                    "Start players along a circle",
                    LocationGenerator.Circle.class, "setLocationGenerator", LocationGenerator.class));
            put(LocationGenerator.Arc.class, 
                    new GenericSetAction("Start on Arc", loadIcon("loc-arc"),
                    "Start players along an arc",
                    LocationGenerator.Arc.class, "setLocationGenerator", LocationGenerator.class));
            put(LocationGenerator.RandomBox.class, 
                    new GenericSetAction("Start at Random Positions", loadIcon("loc-random"),
                    "Start at random locations within a rectangle",
                    LocationGenerator.RandomBox.class, "setLocationGenerator", LocationGenerator.class));

            put(Sensor.NO_SENSOR.getClass(), 
                    new GenericSetAction("No Sensor", loadIcon("sensor-none"),
                    "Do not sense any other players",
                    Sensor.NO_SENSOR, "setSensor", Sensor.class));
            put(Sensor.GLOBAL_SENSOR.getClass(), 
                    new GenericSetAction("Global Sensor", loadIcon("sensor-all"),
                    "Detect all other players",
                    Sensor.GLOBAL_SENSOR, "setSensor", Sensor.class));
            put(Sensor.Radial.class, 
                    new GenericSetAction("Radial Sensor", loadIcon("sensor-radial"),
                    "Detect all players within a given radius",
                    Sensor.Radial.class, "setSensor", Sensor.class));
            put(Sensor.Wedge.class, 
                    new GenericSetAction("Wedge Sensor", loadIcon("sensor-arc"),
                    "Detect all players within a given radius and relative bearing",
                    Sensor.Wedge.class, "setSensor", Sensor.class));

            put(Tasker.SpiralSearch.class,
                    new GenericSetAction("Spiral Search", loadIcon("task-spiral"),
                    "Spiral search pattern",
                    Tasker.SpiralSearch.class, "setTasker", Tasker.class));
            put(Tasker.Follow.class,
                    new GenericSetAction("Follow Target", loadIcon("task-follow"),
                    "Seek/flee another player",
                    Tasker.Follow.class, "setTasker", Tasker.class));
            put(Tasker.Closest.class,
                    new GenericSetAction("Closest Target", loadIcon("task-closest"),
                    "Seek/flee closest target",
                    Tasker.Closest.class, "setTasker", Tasker.class));
            put(Tasker.ClosestTwo.class,
                    new GenericSetAction("Closest Two Targets", loadIcon("task-2closest"),
                    "Seek/flee position between two closest targets",
                    Tasker.ClosestTwo.class, "setTasker", Tasker.class));
            put(Tasker.CenterOfMass.class,
                    new GenericSetAction("Center-of-Mass of Targets", loadIcon("task-center"),
                    "Seek/flee center-of-mass of targets",
                    Tasker.CenterOfMass.class, "setTasker", Tasker.class));
            put(Tasker.Gradient.class,
                    new GenericSetAction("Gradient-Optimized Direction", loadIcon("task-gradient"),
                    "Seek/flee gradient-based optimal direction",
                    Tasker.Gradient.class, "setTasker", Tasker.class));
            put(Tasker.AverageHeading.class,
                    new GenericSetAction("Average Heading of Targets", loadIcon("task-heading"),
                    "Seek/flee average heading of targets",
                    Tasker.AverageHeading.class, "setTasker", Tasker.class));
            put(Tasker.AverageHeadingWeighted.class,
                    new GenericSetAction("Average Heading of Targets (Weighted)", loadIcon("task-heading-weighted"),
                    "Seek/flee average heading of targets, weighted by distance",
                    Tasker.AverageHeadingWeighted.class, "setTasker", Tasker.class));
            put(Tasker.ControlClosest.class,
                    new GenericSetAction("Greedy Target Assignment", loadIcon("task-greedy"),
                    "Assign pursuers to targets using a greedy algorithm",
                    Tasker.ControlClosest.class, "setTasker", Tasker.class));
            put(Tasker.ControlOptimal.class,
                    new GenericSetAction("Optimal Target Assignment", loadIcon("task-optimal"),
                    "Assign pursuers to targets using an optimal algorithm",
                    Tasker.ControlOptimal.class, "setTasker", Tasker.class));

            put(TaskChooser.MAX_CHOOSER.getClass(), 
                    new GenericSetAction("Maximum Priority Task", loadIcon("chooser-max"),
                    "Choose the task with highest priority",
                    TaskChooser.MAX_CHOOSER, "setTaskChooser", TaskChooser.class));
            put(TaskChooser.GRADIENT_CHOOSER.getClass(), 
                    new GenericSetAction("Gradient Combination", loadIcon("chooser-grad"),
                    "Combine multiple tasks via priority-based gradient",
                    TaskChooser.GRADIENT_CHOOSER, "setTaskChooser", TaskChooser.class));

            put(Router.DEFAULT_INSTANCE.getClass(), 
                    new GenericSetAction("Straight to Target", loadIcon("pursuit-straight"),
                    "Route the player straight to the task",
                    Router.DEFAULT_INSTANCE, "setRouter", Router.class));
            put(Router.Leading.class, 
                    new GenericSetAction("Lead Target (CATD)", loadIcon("pursuit-leading"),
                    "Route the player ahead of the task (straight-line optimal)",
                    Router.Leading.class, "setRouter", Router.class));
            put(Router.PluckerLeading.class, 
                    new GenericSetAction("Lead Target by Distance", loadIcon("pursuit-pleading"),
                    "Route the player ahead of the task (based on distance to player)",
                    Router.PluckerLeading.class, "setRouter", Router.class));
            put(Router.ConstantBearing.class, 
                    new GenericSetAction("Constant Bearing", loadIcon("pursuit-angle"),
                    "Route the player at a constant relative bearing task",
                    Router.ConstantBearing.class, "setRouter", Router.class));
            put(Router.Noisy.class, 
                    new GenericSetAction("Noisy Router", loadIcon("pursuit-noisy"),
                    "Route the player using another router, with noise added",
                    Router.Noisy.class, "setRouter", Router.class));
        }
    };

    public static List<Action> getActionsBySuper(Class cls) {
        ArrayList<Action> result = new ArrayList<Action>();
        for (Entry<Class, Action> en : CLASS_ACTIONS.entrySet())
            if (cls.isAssignableFrom(en.getKey()))
                result.add(en.getValue());
        return result;
    }

    public static Icon getIcon(Class cls) {
        Action ac = CLASS_ACTIONS.get(cls);
        if (ac == null)
            return null;
        return (Icon) ac.getValue(Action.SMALL_ICON);
    }

    static void updateLabel(JLabel label, Object obj, boolean textVisible, boolean iconVisible, int size) {
        if (obj == null) {
            label.setText(null);
            label.setIcon(null);
            label.setToolTipText(null);
        } else {
            Action ac = CLASS_ACTIONS.get(obj.getClass());
            if (ac == null)
                return;
            label.setText(textVisible ? (String) ac.getValue(Action.NAME) : null);
            if (iconVisible) {
                ImageIcon icon = (ImageIcon) ac.getValue(Action.SMALL_ICON);
                if (icon.getImage().getWidth(null) == size)
                    label.setIcon(icon);
                else
                    label.setIcon(new ImageIcon(icon.getImage().getScaledInstance(size, size, Image.SCALE_SMOOTH)));
            } else
                label.setIcon(null);
            label.setToolTipText((String) ac.getValue(Action.SHORT_DESCRIPTION));
        }
    }

    /**
     * An action that instantiates a new object (or returns a static constant),
     * and sets it using a method in the receiver class.
     */
    public static class GenericSetAction extends AbstractAction {
        /** The method used to construct a new instance; either this or setObject is used. */
        Constructor constructor;
        /** A single object used to set; either this or constructor is used. */
        Object setObject;
        /** The type of object being set */
        Class setType;

        /** The object receiving the set action */
        Object receiver;
        /** The name of the method used to set */
        String setterName;

        public GenericSetAction(
                String name, Icon icon, String description,
                Object source) {
            putValue(Action.NAME, name);
            putValue(Action.SMALL_ICON, icon);
            putValue(Action.SHORT_DESCRIPTION, description);

            if (source instanceof Class) {
                try { this.constructor = ((Class)source).getConstructor(); } catch (Exception ex) {}
                this.setObject = null;
            } else {
                this.setObject = source;
                this.constructor = null;
            }
        }

        public GenericSetAction(
                String name, Icon icon, String description,
                Object source, String setterName, Class setType) {
            this(name, icon, description, source);
            this.setterName = setterName;
            this.setType = setType;
        }

        @Override public String toString() {
            return "GenericSetAction[" + constructor + "; " + setterName + "]";
        }

        public void actionPerformed(ActionEvent e) {
            if (receiver == null || setterName == null || (constructor == null && setObject == null))
                return;
            Object toSet = null;
            if (constructor == null)
                toSet = setObject;
            else
                try { // obtain the object that will be used to set
                    toSet = constructor.newInstance();
                } catch (Exception ex) {
                    System.out.println("GenericSetAction: failed to create a new object instance: " + ex);
                }
            try { // first try to use the set method on the receiver itself
                Method setMethod = receiver.getClass().getMethod(setterName, setType);
                setMethod.invoke(receiver, toSet);
                return;
            } catch (Exception ex) {
                System.out.println("GenericSetAction: failed to set @ receiver: " + ex);
            }
            try { // next try the set command on the receiver's parameters (if they exist)
                Object parameters = receiver.getClass().getMethod("getParameters").invoke(receiver);
                Method setMethod = setMethod = parameters.getClass().getMethod(setterName, setType);
                setMethod.invoke(parameters, toSet);
                return;
            } catch (Exception ex) {
                System.out.println("GenericSetAction: failed to set @ parameters: " + ex);
            }
            try { // next try the set command on the agent parameters for the team
                AgentParameters parameters = ((Team)receiver).getParameters().getDefaultAgentParameters();
                Method setMethod = setMethod = AgentParameters.class.getMethod(setterName, setType);
                setMethod.invoke(parameters, toSet);
                return;
            } catch (Exception ex) {
                System.out.println("GenericSetAction: failed to set @ default parameters: " + ex);
            }
        }
    }
}
